Authorization & Permissions
Authentication tells a product who a user is. Authorization — what they may do — is
decided by each product, not by auth.abair.ie. ABAIR uses one shared pattern for it across every
project.

Placeholder — replace
assets/authorization-example.pngwith a real screenshot of the CUL Staff Portal User Management page (the role-checkbox editor).
Model
Every product reads from a single shared table, auth_user_roles, and enforces access with
Postgres row-level security (RLS):
- The table has one row per user and one boolean column per role (e.g.
staff,admin,litreoir_admin). A role is granted by setting its column totrue. - It is cross-project: a single user's row holds the role flags for every ABAIR product at once.
- Users can read only their own row — they can check their own roles, but cannot see or change anyone else's.
Two layers act on those flags:
- Client-side gating — the app hides admin UI or redirects users who lack a role. This is a convenience only and can be bypassed.
- Database row-level security — the authoritative gate. Each protected table has policies that allow a row to be read or written only when the current user holds the right role. This runs inside the database and cannot be bypassed from the browser.
Roles
Roles are just boolean columns; new ones are added as products need them. The set in use today (indicative — it grows over time):
| Role | Product | Grants |
|---|---|---|
staff | CUL staff portal | Baseline access to the portal |
admin | CUL | Full admin, including managing other users' roles |
mailing / inbox | CUL | The mailing tool / the shared inbox |
backoffice | Backoffice | Backoffice services |
translations | Translations Editor | The translations editor |
mgnn_admin | Míle Glór na nÓg | Manage stories & recordings |
mao_teacher | Meall an Óige | Teacher access |
litreoir_teacher | Litreoir | Manage your own content |
litreoir_admin | Litreoir | Manage shared / public content |
A single user can hold roles across several products at once.
How access is enforced
A product defines small database helper functions that answer "does the current user hold role X?" and references them from its row-level-security policies. The shape is always the same:
-- "is the signed-in user an admin?"
create function is_admin() returns boolean as $$
select exists (
select 1 from auth_user_roles
where user_id = auth.uid() and admin = true
);
$$ language sql security definer;
-- only admins may add a row
create policy "admins can insert"
on some_table for insert
with check ( is_admin() );
Two policy shapes recur:
- Owner-or-admin — a user may edit rows they created; admins may edit any.
- Public vs. own — admins manage shared/public content; ordinary role-holders manage only what they created.
Privileged server actions that must act across all users (for example, listing every account) run with elevated database access, and so re-check the caller's role themselves before proceeding.
Granting roles
Roles are managed from the CUL "ABAIR Staff Portal", whose User Management screen lists all users and lets an admin tick role checkboxes. (They can also be set directly in Supabase by someone with admin database access.) Granting a role requires admin privileges; ordinary users cannot edit the roles table.
Related
- Míle Glór na nÓg → Security documents the same
pattern for the
mgnn_adminrole. - How users get a session in the first place: Redirect Flow and Embedded Flow.